﻿using System;
using System.Collections.Generic;

namespace easysmb
{
    using easysmbrary;
    using easysmbrary.Client;
    using SanlieHash;

    public class EasySMB : IDisposable
    {
        internal const char path_split_01 = '/';
        internal const char path_split_02 = '\\';
        internal const char path_dot = '.';
        internal string read_path(string path) 
        {
            return path.Replace(path_split_02, path_split_01);
        }
        internal string combine_path(params string[] paths)
        {
            var path = string.Empty;
            int i;
            int len = paths.Length - 1;
            if (paths.Length == 1) return read_path(paths[0]);
            path += read_path(paths[0]).TrimEnd(path_split_01);
            for(i = 1; i < len; i++) if (!string.IsNullOrEmpty(paths[i]))path += path_split_01 + read_path(paths[i]).Trim(path_split_01);
            path += path_split_01 + read_path(paths[i]).TrimStart(path_split_01);
            return path;
        }
        internal string read_sharename(string path)
        {
            path = read_path(path).TrimStart(path_split_01);
            var ix = path.IndexOf(path_split_01);
            if (ix == -1) return path;

            return path.Substring(0, ix);
        }
        internal string read_sharepath(string path)
        {
            path = read_path(path).TrimStart(path_split_01);
            var ix = path.IndexOf(path_split_01);
            if (ix == -1) return string.Empty;

            return path.Substring(ix+1);
        }
        internal string read_name(string path)
        {
            path = read_path(path).TrimEnd(path_split_01);
            var ix = path.LastIndexOf(path_split_01);
            if (ix == -1) return path;
            return path.Substring(ix + 1);
        }
        internal string read_ext(string path)
        {
            var name = read_name(path);
            var ix = name.LastIndexOf(path_dot);
            if (ix == -1) return string.Empty;

            return name.Substring(ix+1).ToLower();
        }
        internal string change_name(string path, string newname)
        {
            path= read_path(path).TrimEnd(path_split_01);
            var ix = path.LastIndexOf(path_split_01);
            if (ix == -1) return path;
            return path.Substring(0, ix) + path_split_01 + newname;
        }
        internal string domain { get; set; }
        internal ISMBClient smb_client { get; set; }
        public EasySMBVersion Version { get; internal set; }
        /// <summary>
        /// version : 1 or 2
        /// </summary>
        /// <param name="domain"></param>
        /// <param name="version"></param>
        public EasySMB(string domain, EasySMBVersion version = EasySMBVersion.SMBv2)
        {
            this.domain = domain;
            this.Version = version;

            switch (version)
            {
                case EasySMBVersion.SMBv1: smb_client = new SMB1Client();break;
                default:
                case EasySMBVersion.SMBv2: smb_client = new SMB2Client();break;
            }

            smb_client.Connect(domain, SMBTransportType.DirectTCPTransport);
        }
        public void Login(string username, string password)
        {
            smb_client.Login(domain, username, password);
        }
        internal IEnumerable<string> LoopShares()
        {
            var shares = smb_client.ListShares(out var status);
            if (status == NTStatus.STATUS_SUCCESS) foreach (var share in shares) yield return share;
        }
        internal IEnumerable<FileDirectoryInformation> ListDirectoryInformation(string share, string path)
        {
            var tree = smb_client.TreeConnect(share, out var status);
            if (status != NTStatus.STATUS_SUCCESS) goto END;
            status = tree.CreateFile(out object handle, out FileStatus fileStatus, path, (AccessMask)DirectoryAccessMask.FILE_LIST_DIRECTORY, 0, ShareAccess.Read, CreateDisposition.FILE_OPEN, CreateOptions.FILE_DIRECTORY_FILE, null);
            if (status != NTStatus.STATUS_SUCCESS) goto END;
            try
            {
                var hash = string.Empty;
                var prehash = string.Empty;
                while (status != NTStatus.STATUS_NO_MORE_FILES)
                {
                    status = tree.QueryDirectory(out List<QueryDirectoryFileInformation> infos, handle, "*", FileInformationClass.FileDirectoryInformation);
                    if (status != NTStatus.STATUS_NO_MORE_FILES && status != NTStatus.STATUS_SUCCESS) goto END;
                    var len = infos.Count;
                    for (var i = 0; i < len; i++)
                    {
                        var info = infos[i];
                        if (info is FileDirectoryInformation info_1)
                        {
                            if ((info_1.FileAttributes & easysmbrary.FileAttributes.Directory) == 0) yield return info_1;
                            else
                            {
                                // 文件夹
                                if (info_1.FileName == "." || info_1.FileName == "..") continue;
                                yield return info_1;
                            }
                            hash = FastHash.GetSM3(hash + info_1.FileName);
                        }
                    }

                    if (hash == string.Empty) break;
                    if (prehash == hash) break;
                    prehash = hash;
                    hash = string.Empty;
                }
            }
            finally
            {
                tree.CloseFile(handle);
            }
        END:;
        }

        internal IEnumerable<string> ListDirectory(string sharename, string path)
        {
            foreach (var info in ListDirectoryInformation(sharename, path))
            {
                if ((info.FileAttributes & easysmbrary.FileAttributes.Directory) == 0) yield return combine_path(sharename, path, info.FileName);
                else yield return combine_path(sharename, path, info.FileName + path_split_01);
            }
        }

        public IEnumerable<string> LoopPathnames(string path)
        {
            var sharename = read_sharename(path);
            if (string.IsNullOrEmpty(sharename)) return LoopShares();

            return ListDirectory(sharename, read_sharepath(path));
        }

        public IEnumerable<EasySMBPath> LoopPaths(string path)
        {
            var sharename = read_sharename(path);
            path = read_sharepath(path);
            if (string.IsNullOrEmpty(sharename))
            {
                foreach (var name in LoopShares()) yield return GetPath(name);
            }
            else
            {
                foreach (var info in ListDirectoryInformation(sharename, path)) 
                    if ((info.FileAttributes & easysmbrary.FileAttributes.Directory) != 0)
                        yield return new EasySMBPath(this, combine_path(sharename, path, info.FileName + path_split_01));
            }
        }
        public IEnumerable<EasySMBFile> LoopFiles(string path)
        {
            var sharename = read_sharename(path);
            path = read_sharepath(path);
            if (!string.IsNullOrEmpty(sharename))
            {
                foreach (var info in ListDirectoryInformation(sharename, path))
                    if ((info.FileAttributes & easysmbrary.FileAttributes.Directory) == 0)
                        yield return new EasySMBFile(this, combine_path(sharename, path, info.FileName), info);
            }
        }
        public bool Exist(string path)
        {
            var tree = smb_client.TreeConnect(read_sharename(path), out var status);
            if (status != NTStatus.STATUS_SUCCESS) return false;

            status = tree.CreateFile(out object handle, out FileStatus fileStatus, read_sharepath(path), (AccessMask)DirectoryAccessMask.FILE_LIST_DIRECTORY, 0, ShareAccess.Read, CreateDisposition.FILE_OPEN, CreateOptions.FILE_DIRECTORY_FILE, null);
            if (status == NTStatus.STATUS_SUCCESS)
            {
                if (handle != null) tree.CloseFile(handle);
                return true;
            }
            // 判断文件
            if(status == NTStatus.STATUS_NOT_A_DIRECTORY) return true;
            return false;
        }
        public bool IsFile(string path)
        {
            var tree = smb_client.TreeConnect(read_sharename(path), out var status);
            if (status != NTStatus.STATUS_SUCCESS) return false;
            status = tree.CreateFile(out object handle, out FileStatus fileStatus, read_sharepath(path), (AccessMask)DirectoryAccessMask.FILE_LIST_DIRECTORY, 0, ShareAccess.Read, CreateDisposition.FILE_OPEN, CreateOptions.FILE_DIRECTORY_FILE, null);
            if (status == NTStatus.STATUS_NOT_A_DIRECTORY) return true;
            if (handle != null) tree.CloseFile(handle);
            return false;
        }
        internal void DeleteFile(string path)
        {
            var tree = smb_client.TreeConnect(read_sharename(path), out var status);
            if (status != NTStatus.STATUS_SUCCESS) goto END;
            status = tree.CreateFile(out object handle, out FileStatus fileStatus, read_sharepath(path), AccessMask.DELETE, 0, ShareAccess.Delete, CreateDisposition.FILE_OPEN, CreateOptions.FILE_NON_DIRECTORY_FILE, null);
            if (status != NTStatus.STATUS_SUCCESS) goto END;

            FileDispositionInformation fileDispositionInfo = new FileDispositionInformation();
            fileDispositionInfo.DeletePending = true;
            status = tree.SetFileInformation(handle, fileDispositionInfo);
            if (handle != null) tree.CloseFile(handle);
        END:;
        }
        internal void Rename(string path, string newName)
        {
            CreateOptions createOptions = 0;
            
            var tree = smb_client.TreeConnect(read_sharename(path), out var status);
            if (status != NTStatus.STATUS_SUCCESS) goto END;

            status = tree.CreateFile(out object handle, out FileStatus fileStatus, read_sharepath(path), (AccessMask)DirectoryAccessMask.FILE_LIST_DIRECTORY, 0, ShareAccess.Read, CreateDisposition.FILE_OPEN, CreateOptions.FILE_DIRECTORY_FILE, null);
            if (status == NTStatus.STATUS_NOT_A_DIRECTORY) createOptions = CreateOptions.FILE_NON_DIRECTORY_FILE;
            if (handle != null) tree.CloseFile(handle);
            
            status = tree.CreateFile(out handle, out fileStatus, read_sharepath(path), AccessMask.DELETE, 0, ShareAccess.Read | ShareAccess.Write, CreateDisposition.FILE_OPEN, createOptions, null);
            if (status != NTStatus.STATUS_SUCCESS) goto END;
            FileRenameInformationType2 renameInfo = new FileRenameInformationType2();
            renameInfo.ReplaceIfExists = false;
            renameInfo.FileName = newName;
            status = tree.SetFileInformation(handle, renameInfo);
            if (handle != null) tree.CloseFile(handle);
            END:;
        }
        public EasySMBFile GetFile(string path)
        {
            var tree = smb_client.TreeConnect(read_sharename(path), out var status);
            if (status != NTStatus.STATUS_SUCCESS) goto END;
            status = tree.CreateFile(out object handle, out FileStatus fileStatus, read_sharepath(path), (AccessMask)FileAccessMask.FILE_READ_ATTRIBUTES, 0, ShareAccess.Read, CreateDisposition.FILE_OPEN, 0, null);
            if (status != NTStatus.STATUS_SUCCESS) goto END;
            var fileInfo = NTFileStoreHelper.GetNetworkOpenInformation(tree, handle);
            tree.CloseFile(handle);
            return new EasySMBFile(this, path, fileInfo);
        END:;
            return null;
        }
        internal EasySMBFile NewFile(string path)
        {
            var tree = smb_client.TreeConnect(read_sharename(path), out var status);
            if (status != NTStatus.STATUS_SUCCESS) goto END;
            status = tree.CreateFile(out object handle, out FileStatus fileStatus, read_sharepath(path), (AccessMask)FileAccessMask.FILE_READ_ATTRIBUTES, easysmbrary.FileAttributes.Archive, ShareAccess.Write, CreateDisposition.FILE_CREATE | CreateDisposition.FILE_OPEN, 0, null);
            if (status != NTStatus.STATUS_SUCCESS) goto END;
            var fileInfo = NTFileStoreHelper.GetNetworkOpenInformation(tree, handle);
            tree.CloseFile(handle);
            return new EasySMBFile(this, path, fileInfo);
        END:;
            return null;
        }
        public EasySMBPath GetPath(string path)
        {
            var tree = smb_client.TreeConnect(read_sharename(path), out var status);
            if (status != NTStatus.STATUS_SUCCESS) goto END;
            status = tree.CreateFile(out object handle, out FileStatus fileStatus, read_sharepath(path), (AccessMask)FileAccessMask.FILE_READ_ATTRIBUTES, 0, ShareAccess.Read, CreateDisposition.FILE_OPEN, CreateOptions.FILE_DIRECTORY_FILE, null);
            if (status != NTStatus.STATUS_SUCCESS) goto END;
            tree.GetFileInformation(out var info, handle, FileInformationClass.FileBasicInformation);
            tree.CloseFile(handle);
            if (info is FileBasicInformation info_1) return new EasySMBPath(this, path, info_1);
            return new EasySMBPath(this, path);
        END:;
            return null;
        }
        internal void CreatePath(string path)
        {
            var tree = smb_client.TreeConnect(read_sharename(path), out var status);
            if (status != NTStatus.STATUS_SUCCESS) goto END;
            status = tree.CreateFile(out object handle, out FileStatus fileStatus, read_sharepath(path), (AccessMask)DirectoryAccessMask.FILE_ADD_SUBDIRECTORY, 0, ShareAccess.Read | ShareAccess.Write, CreateDisposition.FILE_CREATE, CreateOptions.FILE_DIRECTORY_FILE, null);
            if (status != NTStatus.STATUS_SUCCESS) goto END;
            tree.CloseFile(handle);
        END:;
        }
        internal void DeletePath(string path)
        {
            var tree = smb_client.TreeConnect(read_sharename(path), out var status);
            if (status != NTStatus.STATUS_SUCCESS) goto END;
            status = tree.CreateFile(out object handle, out FileStatus fileStatus, read_sharepath(path), AccessMask.DELETE, 0, ShareAccess.Delete, CreateDisposition.FILE_OPEN, CreateOptions.FILE_DIRECTORY_FILE, null);
            if (status != NTStatus.STATUS_SUCCESS) goto END;
            FileDispositionInformation fileDispositionInfo = new FileDispositionInformation();
            fileDispositionInfo.DeletePending = true;
            status = tree.SetFileInformation(handle, fileDispositionInfo);
            tree.CloseFile(handle);
        END:;
        }

        public void Dispose()
        {
            if (smb_client != null && smb_client.IsConnected)
            {
                smb_client.Disconnect();
                smb_client = null;
            }
        }
    }
}
